package chapter5;

import lombok.ToString;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @author caiya
 * @date 2020/3/29 21:30
 */
@Slf4j
public class DelayQueueTest {
    static DelayQueue<DelayedEle> delayQueue = new DelayQueue<>();

    public static void main(String[] args) throws InterruptedException {
        /**
         * DelayQueue：无界延迟队列
         * 1、原理：
         *      内部使用 PriorityQueue 存放元素，队列元素需要实现 Delayed 接口以便获取过期时间
         * 2、boolean offer(E e)：
         *      1）e 为 null，抛出 NPE
         *      2）PriorityQueue 为无界的，所以 offer 一直返回 true
         * 3、E take()：阻塞获取已过期的队列元素，获取不到时阻塞等待
         * 4、E poll()：非阻塞获取队首过期元素，没有的话则返回 null
         * 5、int size()：返回队列大小
         */
        for (int i = 0; i < 10; i++) {
            DelayedEle delayedEle = new DelayedEle("task" + i, ThreadLocalRandom.current().nextInt(10000));
            delayQueue.offer(delayedEle);
        }

        DelayedEle delayedEle = null;
        for (;;) {
            while ((delayedEle = delayQueue.take()) != null) {
                log.debug(delayedEle.toString());
            }
        }
    }

    @ToString
    static class DelayedEle implements Delayed {
        // 到期时间
        private final long expireTime;
        // 延迟时间
        private final long delayTime;
        // 任务名称
        private String taskName;

        public DelayedEle(String taskName, long delay) {
            this.delayTime = delay;
            this.taskName = taskName;
            this.expireTime = System.currentTimeMillis() + delay;
        }

        /**
         * 获取剩余时间 = 过期时间 - 当前时间
         * @param unit
         * @return
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return this.expireTime - System.currentTimeMillis();
        }

        /**
         * 剩余时间由小到大排序
         * @param o
         * @return
         */
        @Override
        public int compareTo(Delayed o) {
            return (int) (this.getDelay(TimeUnit.MICROSECONDS) - o.getDelay(TimeUnit.MILLISECONDS));
        }
    }
}
